﻿using System;
using System.Collections.Generic;
using System.IO;

namespace NetAssist.IO
{
  /// <summary>
  /// Provide methods that enumerate file content yielding data blocks as they are requested. 
  /// </summary>
  public static class BinaryFile
  {
    /// <summary>
    /// Enumerate a the file at the provided path reading fixed size blocks.
    /// </summary>
    /// <param name="path">Path of the file to enumerate.</param>
    /// <param name="blockSize">The read block size.</param>
    /// <returns>An IEnumerable&lt;BinaryFileBlock&gt; that enables reading the file in blocks.</returns>
    /// <remarks>Last block in the enumeration might have less data than the blockSize.</remarks>
    public static IEnumerable<BinaryFileBlock> ReadFile(string path, int blockSize)
    {
      using (Stream file = File.OpenRead(path))
        foreach (BinaryFileBlock block in file.ReadBlocks(blockSize))
          yield return block;
    }

    /// <summary>
    /// Enumerate a the file at the provided path reading fixed size blocks.
    /// </summary>
    /// <param name="file">The stream to use for the enumeration.</param>
    /// <param name="blockSize">The read block size.</param>
    /// <returns>An IEnumerable&lt;BinaryFileBlock&gt; that enables reading the file in blocks.</returns>
    /// <remarks>Last block in the enumeration might have less data than the blockSize.</remarks>
    public static IEnumerable<BinaryFileBlock> ReadBlocks(this Stream file, int blockSize)
    {
      int index = 0;
      int offset = 0;
      int read = 0;
      byte[] arr = new byte[blockSize];

      do
      {
        read = file.Read(arr, 0, blockSize);
        if (read == blockSize)
          yield return new BinaryFileBlock(index, offset, arr);
        else
        {
          if (read != 0)
          {
            byte[] tempArr = new byte[read];
            Array.Copy(arr, tempArr, read);

            yield return new BinaryFileBlock(index, offset, tempArr);
          }
          yield break;
        }
        offset += read;
        index++;
      } while (true);
    }

    /// <summary>
    /// Enumerate a the file at the provided path reading fixed size blocks.
    /// </summary>
    /// <param name="path">Path of the file to enumerate.</param>    
    /// <param name="read">A method that reads the file returning a byte array of the expected size.</param>
    /// <returns>An IEnumerable&lt;BinaryFileBlock&gt; that enables reading the file in blocks.</returns>
    /// <remarks>Last block in the enumeration might have less data than the blockSize.</remarks>           
    public static IEnumerable<BinaryFileBlock> ReadFile(string path, Func<Stream, byte[]> read)
    {
      using (Stream file = File.OpenRead(path))
        foreach (BinaryFileBlock block in file.ReadBlocks(read))
          yield return block;
    }

    /// <summary>
    /// Enumerate a the file at the provided path reading fixed size blocks.
    /// </summary>
    /// <param name="file">The stream to use for the enumeration.</param>
    /// <param name="read">A method that reads the file returning a byte array of the expected size.</param>
    /// <returns>An IEnumerable&lt;BinaryFileBlock&gt; that enables reading the file in blocks.</returns>
    /// <remarks>Last block in the enumeration might have less data than the blockSize.</remarks>           
    public static IEnumerable<BinaryFileBlock> ReadBlocks(this Stream file, Func<Stream, byte[]> read)
    {
      int index = 0;
      int offset = 0;
      do
      {
        byte[] res = read(file);
        if (res == null || res.Length == 0)
          yield break;

        yield return new BinaryFileBlock(index, offset, res);
        offset += res.Length;
        index++;
      } while (true);
    }
  }
}
